
interface IAsyncIterator<T> extends AsyncIterator<T, void> { }

export interface IAsyncIterableIterator<T> extends IAsyncIterator<T>, AsyncIterable<T> { }

export interface Deferred<T> {
    readonly promise: Promise<T>;
    readonly resolved: boolean;
    readonly rejected: boolean;
    readonly completed: boolean;
    resolve(value?: T | PromiseLike<T>): void;
    reject(reason?: string | Error | Record<string, unknown>): void;
}

class DeferredImpl<T> implements Deferred<T> {
    constructor(private scope: any = null) {
        this._promise = new Promise<T>((res, rej) => {
            this._resolve = res;
            this._reject = rej;
        });
    }

    private _resolve!: (value: T | PromiseLike<T>) => void;

    private _reject!: (reason?: string | Error | Record<string, unknown>) => void;

    private _resolved = false;

    private _rejected = false;

    private _promise: Promise<T>;

    get promise(): Promise<T> {
        return this._promise;
    }

    get resolved(): boolean {
        return this._resolved;
    }

    get rejected(): boolean {
        return this._rejected;
    }

    get completed(): boolean {
        return this._rejected || this._resolved;
    }

    resolve(_value: T | PromiseLike<T>): void {
        this._resolve.apply(this.scope ? this.scope : this, [_value]);
        this._resolved = true;
    }
    reject(_reason: string | Error | Record<string, unknown>): void {
        this._reject.apply(this.scope ? this.scope : this, [_reason]);
        this._rejected = true;
    }
}
export function createDeferred<T = void>(scope: any = null): Deferred<T> {
    return new DeferredImpl<T>(scope);
}

type NextResult<T> = { index: number } & (
    | { result: IteratorResult<T, T | void>; err: null }
    | { result: null; err: Error }
);

async function getNext<T>(it: AsyncIterator<T, T | void>, indexMaybe?: number): Promise<NextResult<T>> {
    const index = indexMaybe === undefined ? -1 : indexMaybe;
    try {
        const result = await it.next();
        return { index, result, err: null };
    } catch (err) {
        return { index, err: err as Error, result: null };
    }
}

const NEVER: Promise<unknown> = new Promise(() => {
    /** No body. */
});

export async function* chain<T>(
    iterators: AsyncIterator<T, T | void>[],
    onError?: (err: Error, index: number) => Promise<void>,
    // Ultimately we may also want to support cancellation.
): IAsyncIterableIterator<T> {
    const promises = iterators.map(getNext);
    let numRunning = iterators.length;
    while (numRunning > 0) {
        // Promise.race will not fail, because each promise calls getNext,
        // Which handles failures by wrapping each iterator in a try/catch block.
        const { index, result, err } = await Promise.race(promises);

        if (err !== null) {
            promises[index] = NEVER as Promise<NextResult<T>>;
            numRunning -= 1;
            if (onError !== undefined) {
                await onError(err, index);
            }
            // XXX Log the error.
        } else if (result!.done) {
            promises[index] = NEVER as Promise<NextResult<T>>;
            numRunning -= 1;
            // If R is void then result.value will be undefined.
            if (result!.value !== undefined) {
                yield result!.value;
            }
        } else {
            promises[index] = getNext(iterators[index], index);
            // Only the "return" result can be undefined (void),
            // so we're okay here.
            yield result!.value as T;
        }
    }
}

export function iterEmpty<T>(): IAsyncIterableIterator<T> {
    return ((async function* () {
        /** No body. */
    })() as unknown) as IAsyncIterableIterator<T>;
}

export function isPromise<T>(v: any): v is Promise<T> {
    return typeof v?.then === 'function' && typeof v?.catch === 'function';
}